package net.borderwars.jedit;
/*
 * InputHandler.java - Manages key bindings and executes actions
 * Copyright (C) 1999 Slava Pestov
 *
 * You may use and modify this package for any purpose. Redistribution is
 * permitted, in both source and binary form, provided that this notice
 * remains intact in all source distributions of this package.
 */

import javax.swing.*;
import javax.swing.text.BadLocationException;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.Hashtable;

/**
 * An input handler converts the user's key strokes into concrete actions.
 * It also takes care of macro recording and action repetition.<p>
 * <p/>
 * This class provides all the necessary support code for an input
 * handler, but doesn't actually do any key binding logic. It is up
 * to the implementations of this class to do so.
 *
 * @author Slava Pestov
 * @version $Id: InputHandler.java,v 1.14 1999/12/13 03:40:30 sp Exp $
 * @see org.gjt.sp.jedit.textarea.DefaultInputHandler
 */
public abstract class InputHandler extends KeyAdapter {
    /**
     * If this client property is set to Boolean.TRUE on the text area,
     * the home/end keys will support 'smart' BRIEF-like behaviour
     * (one press = start/end of line, two presses = start/end of
     * viewscreen, three presses = start/end of document). By default,
     * this property is not set.
     */
    public static final String SMART_HOME_END_PROPERTY = "InputHandler.homeEnd";

    public static final ActionListener BACKSPACE = new backspace ();
    public static final ActionListener BACKSPACE_WORD = new backspace_word ();
    public static final ActionListener DELETE = new delete ();
    public static final ActionListener DELETE_WORD = new delete_word ();
    public static final ActionListener END = new end (false);
    public static final ActionListener DOCUMENT_END = new document_end (false);
    public static final ActionListener SELECT_END = new end (true);
    public static final ActionListener SELECT_DOC_END = new document_end (true);
    public static final ActionListener INSERT_BREAK = new insert_break ();
    public static final ActionListener INSERT_TAB = new insert_tab ();
    public static final ActionListener HOME = new home (false);
    public static final ActionListener DOCUMENT_HOME = new document_home (false);
    public static final ActionListener SELECT_HOME = new home (true);
    public static final ActionListener SELECT_DOC_HOME = new document_home (true);
    public static final ActionListener NEXT_CHAR = new next_char (false);
    public static final ActionListener NEXT_LINE = new next_line (false);
    public static final ActionListener NEXT_PAGE = new next_page (false);
    public static final ActionListener NEXT_WORD = new next_word (false);
    public static final ActionListener SELECT_NEXT_CHAR = new next_char (true);
    public static final ActionListener SELECT_NEXT_LINE = new next_line (true);
    public static final ActionListener SELECT_NEXT_PAGE = new next_page (true);
    public static final ActionListener SELECT_NEXT_WORD = new next_word (true);
    public static final ActionListener OVERWRITE = new overwrite ();
    public static final ActionListener PREV_CHAR = new prev_char (false);
    public static final ActionListener PREV_LINE = new prev_line (false);
    public static final ActionListener PREV_PAGE = new prev_page (false);
    public static final ActionListener PREV_WORD = new prev_word (false);
    public static final ActionListener SELECT_PREV_CHAR = new prev_char (true);
    public static final ActionListener SELECT_PREV_LINE = new prev_line (true);
    public static final ActionListener SELECT_PREV_PAGE = new prev_page (true);
    public static final ActionListener SELECT_PREV_WORD = new prev_word (true);
    public static final ActionListener REPEAT = new repeat ();
    public static final ActionListener TOGGLE_RECT = new toggle_rect ();

    // Default action
    public static final ActionListener INSERT_CHAR = new insert_char ();

    private static Hashtable actions;

    static {
        actions = new Hashtable ();
        actions.put ("backspace", BACKSPACE);
        actions.put ("backspace-word", BACKSPACE_WORD);
        actions.put ("delete", DELETE);
        actions.put ("delete-word", DELETE_WORD);
        actions.put ("end", END);
        actions.put ("select-end", SELECT_END);
        actions.put ("document-end", DOCUMENT_END);
        actions.put ("select-doc-end", SELECT_DOC_END);
        actions.put ("insert-break", INSERT_BREAK);
        actions.put ("insert-tab", INSERT_TAB);
        actions.put ("home", HOME);
        actions.put ("select-home", SELECT_HOME);
        actions.put ("document-home", DOCUMENT_HOME);
        actions.put ("select-doc-home", SELECT_DOC_HOME);
        actions.put ("next-char", NEXT_CHAR);
        actions.put ("next-line", NEXT_LINE);
        actions.put ("next-page", NEXT_PAGE);
        actions.put ("next-word", NEXT_WORD);
        actions.put ("select-next-char", SELECT_NEXT_CHAR);
        actions.put ("select-next-line", SELECT_NEXT_LINE);
        actions.put ("select-next-page", SELECT_NEXT_PAGE);
        actions.put ("select-next-word", SELECT_NEXT_WORD);
        actions.put ("overwrite", OVERWRITE);
        actions.put ("prev-char", PREV_CHAR);
        actions.put ("prev-line", PREV_LINE);
        actions.put ("prev-page", PREV_PAGE);
        actions.put ("prev-word", PREV_WORD);
        actions.put ("select-prev-char", SELECT_PREV_CHAR);
        actions.put ("select-prev-line", SELECT_PREV_LINE);
        actions.put ("select-prev-page", SELECT_PREV_PAGE);
        actions.put ("select-prev-word", SELECT_PREV_WORD);
        actions.put ("repeat", REPEAT);
        actions.put ("toggle-rect", TOGGLE_RECT);
        actions.put ("insert-char", INSERT_CHAR);
    }

    /**
     * Returns a named text area action.
     *
     * @param name The action name
     */
    public static ActionListener getAction (String name) {
        return (ActionListener) actions.get (name);
    }

    /**
     * Returns the name of the specified text area action.
     *
     * @param listener The action
     */
    public static String getActionName (ActionListener listener) {
        Enumeration enume = getActions ();
        while (enume.hasMoreElements ()) {
            String name = (String) enume.nextElement ();
            ActionListener _listener = getAction (name);
            if (_listener == listener)
                return name;
        }
        return null;
    }

    /**
     * Returns an enumeration of all available actions.
     */
    public static Enumeration getActions () {
        return actions.keys ();
    }

    /**
     * Adds the default key bindings to this input handler.
     * This should not be called in the constructor of this
     * input handler, because applications might load the
     * key bindings from a file, etc.
     */
    public abstract void addDefaultKeyBindings ();

    /**
     * Adds a key binding to this input handler.
     *
     * @param keyBinding The key binding (the format of this is
     *                   input-handler specific)
     * @param action     The action
     */
    public abstract void addKeyBinding (String keyBinding, ActionListener action);

    /**
     * Removes a key binding from this input handler.
     *
     * @param keyBinding The key binding
     */
    public abstract void removeKeyBinding (String keyBinding);

    /**
     * Removes all key bindings from this input handler.
     */
    public abstract void removeAllKeyBindings ();

    /**
     * Grabs the next key typed event and invokes the specified
     * action with the key as a the action command.
     *
     * @param action The action
     */
    public void grabNextKeyStroke (ActionListener listener) {
        grabAction = listener;
    }

    /**
     * Returns if repeating is enabled. When repeating is enabled,
     * actions will be executed multiple times. This is usually
     * invoked with a special key stroke in the input handler.
     */
    public boolean isRepeatEnabled () {
        return repeat;
    }

    /**
     * Enables repeating. When repeating is enabled, actions will be
     * executed multiple times. Once repeating is enabled, the input
     * handler should read a number from the keyboard.
     */
    public void setRepeatEnabled (boolean repeat) {
        this.repeat = repeat;
    }

    /**
     * Returns the number of times the next action will be repeated.
     */
    public int getRepeatCount () {
        return (repeat ? Math.max (1, repeatCount) : 1);
    }

    /**
     * Sets the number of times the next action will be repeated.
     *
     * @param repeatCount The repeat count
     */
    public void setRepeatCount (int repeatCount) {
        this.repeatCount = repeatCount;
    }

    /**
     * Returns the macro recorder. If this is non-null, all executed
     * actions should be forwarded to the recorder.
     */
    public InputHandler.MacroRecorder getMacroRecorder () {
        return recorder;
    }

    /**
     * Sets the macro recorder. If this is non-null, all executed
     * actions should be forwarded to the recorder.
     *
     * @param recorder The macro recorder
     */
    public void setMacroRecorder (InputHandler.MacroRecorder recorder) {
        this.recorder = recorder;
    }

    /**
     * Returns a copy of this input handler that shares the same
     * key bindings. Setting key bindings in the copy will also
     * set them in the original.
     */
    public abstract InputHandler copy ();

    /**
     * Executes the specified action, repeating and recording it as
     * necessary.
     *
     * @param listener      The action listener
     * @param source        The event source
     * @param actionCommand The action command
     */
    public void executeAction (ActionListener listener, Object source,
                               String actionCommand) {
        // create event
        ActionEvent evt = new ActionEvent (source,
                                           ActionEvent.ACTION_PERFORMED,
                                           actionCommand);

        // don't do anything if the action is a wrapper
        // (like EditAction.Wrapper)
        if (listener instanceof Wrapper) {
            listener.actionPerformed (evt);
            return;
        }

        // remember old values, in case action changes them
        boolean _repeat = repeat;
        int _repeatCount = getRepeatCount ();

        // execute the action
        if (listener instanceof InputHandler.NonRepeatable)
            listener.actionPerformed (evt);
        else {
            for (int i = 0; i < Math.max (1, repeatCount); i++)
                listener.actionPerformed (evt);
        }

        // do recording. Notice that we do no recording whatsoever
        // for actions that grab keys
        if (grabAction == null) {
            if (recorder != null) {
                if (!(listener instanceof InputHandler.NonRecordable)) {
                    if (_repeatCount != 1)
                        recorder.actionPerformed (REPEAT, String.valueOf (_repeatCount));

                    recorder.actionPerformed (listener, actionCommand);
                }
            }

            // If repeat was true originally, clear it
            // Otherwise it might have been set by the action, etc
            if (_repeat) {
                repeat = false;
                repeatCount = 0;
            }
        }
    }

    /**
     * Returns the text area that fired the specified event.
     *
     * @param evt The event
     */
    public static JEditTextArea getTextArea (EventObject evt) {
        if (evt != null) {
            Object o = evt.getSource ();
            if (o instanceof Component) {
                // find the parent text area
                Component c = (Component) o;
                for (; ;) {
                    if (c instanceof JEditTextArea)
                        return (JEditTextArea) c;
                    else
                        if (c == null)
                            break;
                    if (c instanceof JPopupMenu)
                        c = ((JPopupMenu) c)
                                .getInvoker ();
                    else
                        c = c.getParent ();
                }
            }
        }

        // this shouldn't happen
        System.err.println ("BUG: getTextArea() returning null");
        System.err.println ("Report this to Slava Pestov <sp@gjt.org>");
        return null;
    }

    // protected members

    /**
     * If a key is being grabbed, this method should be called with
     * the appropriate key event. It executes the grab action with
     * the typed character as the parameter.
     */
    protected void handleGrabAction (KeyEvent evt) {
        // Clear it *before* it is executed so that executeAction()
        // resets the repeat count
        ActionListener _grabAction = grabAction;
        grabAction = null;
        executeAction (_grabAction, evt.getSource (),
                       String.valueOf (evt.getKeyChar ()));
    }

    // protected members
    protected ActionListener grabAction;
    protected boolean repeat;
    protected int repeatCount;
    protected InputHandler.MacroRecorder recorder;

    /**
     * If an action implements this interface, it should not be repeated.
     * Instead, it will handle the repetition itself.
     */
    public interface NonRepeatable {
    }

    /**
     * If an action implements this interface, it should not be recorded
     * by the macro recorder. Instead, it will do its own recording.
     */
    public interface NonRecordable {
    }

    /**
     * For use by EditAction.Wrapper only.
     *
     * @since jEdit 2.2final
     */
    public interface Wrapper {
    }

    /**
     * Macro recorder.
     */
    public interface MacroRecorder {
        void actionPerformed (ActionListener listener,
                              String actionCommand);
    }

    public static class backspace implements ActionListener {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);

            if (!textArea.isEditable ()) {
                textArea.getToolkit ().beep ();
                return;
            }

            if (textArea.getSelectionStart ()
                    != textArea.getSelectionEnd ()) {
                textArea.setSelectedText ("");
            }
            else {
                int caret = textArea.getCaretPosition ();
                if (caret == 0) {
                    textArea.getToolkit ().beep ();
                    return;
                }
                try {
                    textArea.getDocument ().remove (caret - 1, 1);
                }
                catch (BadLocationException bl) {
                    bl.printStackTrace ();
                }
            }
        }
    }

    public static class backspace_word implements ActionListener {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int start = textArea.getSelectionStart ();
            if (start != textArea.getSelectionEnd ()) {
                textArea.setSelectedText ("");
            }

            int line = textArea.getCaretLine ();
            int lineStart = textArea.getLineStartOffset (line);
            int caret = start - lineStart;

            String lineText = textArea.getLineText (textArea
                    .getCaretLine ());

            if (caret == 0) {
                if (lineStart == 0) {
                    textArea.getToolkit ().beep ();
                    return;
                }
                caret--;
            }
            else {
                String noWordSep = (String) textArea.getDocument ().getProperty ("noWordSep");
                caret = TextUtilities.findWordStart (lineText, caret, noWordSep);
            }

            try {
                textArea.getDocument ().remove (
                        caret + lineStart,
                        start - (caret + lineStart));
            }
            catch (BadLocationException bl) {
                bl.printStackTrace ();
            }
        }
    }

    public static class delete implements ActionListener {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);

            if (!textArea.isEditable ()) {
                textArea.getToolkit ().beep ();
                return;
            }

            if (textArea.getSelectionStart ()
                    != textArea.getSelectionEnd ()) {
                textArea.setSelectedText ("");
            }
            else {
                int caret = textArea.getCaretPosition ();
                if (caret == textArea.getDocumentLength ()) {
                    textArea.getToolkit ().beep ();
                    return;
                }
                try {
                    textArea.getDocument ().remove (caret, 1);
                }
                catch (BadLocationException bl) {
                    bl.printStackTrace ();
                }
            }
        }
    }

    public static class delete_word implements ActionListener {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int start = textArea.getSelectionStart ();
            if (start != textArea.getSelectionEnd ()) {
                textArea.setSelectedText ("");
            }

            int line = textArea.getCaretLine ();
            int lineStart = textArea.getLineStartOffset (line);
            int caret = start - lineStart;

            String lineText = textArea.getLineText (textArea
                    .getCaretLine ());

            if (caret == lineText.length ()) {
                if (lineStart + caret == textArea.getDocumentLength ()) {
                    textArea.getToolkit ().beep ();
                    return;
                }
                caret++;
            }
            else {
                String noWordSep = (String) textArea.getDocument ().getProperty ("noWordSep");
                caret = TextUtilities.findWordEnd (lineText, caret, noWordSep);
            }

            try {
                textArea.getDocument ().remove (start,
                                                (caret + lineStart) - start);
            }
            catch (BadLocationException bl) {
                bl.printStackTrace ();
            }
        }
    }

    public static class end implements ActionListener {
        private boolean select;

        public end (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);

            int caret = textArea.getCaretPosition ();

            int lastOfLine = textArea.getLineEndOffset (
                    textArea.getCaretLine ()) - 1;
            int lastVisibleLine = textArea.getFirstLine ()
                    + textArea.getVisibleLines ();
            if (lastVisibleLine >= textArea.getLineCount ()) {
                lastVisibleLine = Math.min (textArea.getLineCount () - 1,
                                            lastVisibleLine);
            }
            else
                lastVisibleLine -= (textArea.getElectricScroll () + 1);

            int lastVisible = textArea.getLineEndOffset (lastVisibleLine) - 1;
            int lastDocument = textArea.getDocumentLength ();

            if (caret == lastDocument) {
                textArea.getToolkit ().beep ();
                return;
            }
            else
                if (!Boolean.TRUE.equals (textArea.getClientProperty (
                        SMART_HOME_END_PROPERTY)))
                    caret = lastOfLine;
                else
                    if (caret == lastVisible)
                        caret = lastDocument;
                    else
                        if (caret == lastOfLine)
                            caret = lastVisible;
                        else
                            caret = lastOfLine;

            if (select)
                textArea.select (textArea.getMarkPosition (), caret);
            else
                textArea.setCaretPosition (caret);
        }
    }

    public static class document_end implements ActionListener {
        private boolean select;

        public document_end (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            if (select)
                textArea.select (textArea.getMarkPosition (),
                                 textArea.getDocumentLength ());
            else
                textArea.setCaretPosition (textArea
                        .getDocumentLength ());
        }
    }

    public static class home implements ActionListener {
        private boolean select;

        public home (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);

            int caret = textArea.getCaretPosition ();

            int firstLine = textArea.getFirstLine ();

            int firstOfLine = textArea.getLineStartOffset (
                    textArea.getCaretLine ());
            int firstVisibleLine = (firstLine == 0 ? 0 :
                    firstLine + textArea.getElectricScroll ());
            int firstVisible = textArea.getLineStartOffset (
                    firstVisibleLine);

            if (caret == 0) {
                textArea.getToolkit ().beep ();
                return;
            }
            else
                if (!Boolean.TRUE.equals (textArea.getClientProperty (
                        SMART_HOME_END_PROPERTY)))
                    caret = firstOfLine;
                else
                    if (caret == firstVisible)
                        caret = 0;
                    else
                        if (caret == firstOfLine)
                            caret = firstVisible;
                        else
                            caret = firstOfLine;

            if (select)
                textArea.select (textArea.getMarkPosition (), caret);
            else
                textArea.setCaretPosition (caret);
        }
    }

    public static class document_home implements ActionListener {
        private boolean select;

        public document_home (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            if (select)
                textArea.select (textArea.getMarkPosition (), 0);
            else
                textArea.setCaretPosition (0);
        }
    }

    public static class insert_break implements ActionListener {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);

            if (!textArea.isEditable ()) {
                textArea.getToolkit ().beep ();
                return;
            }

            textArea.setSelectedText ("\n");
        }
    }

    public static class insert_tab implements ActionListener {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);

            if (!textArea.isEditable ()) {
                textArea.getToolkit ().beep ();
                return;
            }

            textArea.overwriteSetSelectedText ("\t");
        }
    }

    public static class next_char implements ActionListener {
        private boolean select;

        public next_char (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int caret = textArea.getCaretPosition ();
            if (caret == textArea.getDocumentLength ()) {
                textArea.getToolkit ().beep ();
                return;
            }

            if (select)
                textArea.select (textArea.getMarkPosition (),
                                 caret + 1);
            else
                textArea.setCaretPosition (caret + 1);
        }
    }

    public static class next_line implements ActionListener {
        private boolean select;

        public next_line (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int caret = textArea.getCaretPosition ();
            int line = textArea.getCaretLine ();

            if (line == textArea.getLineCount () - 1) {
                textArea.getToolkit ().beep ();
                return;
            }

            int magic = textArea.getMagicCaretPosition ();
            if (magic == -1) {
                magic = textArea.offsetToX (line,
                                            caret - textArea.getLineStartOffset (line));
            }

            caret = textArea.getLineStartOffset (line + 1)
                    + textArea.xToOffset (line + 1, magic);
            if (select)
                textArea.select (textArea.getMarkPosition (), caret);
            else
                textArea.setCaretPosition (caret);
            textArea.setMagicCaretPosition (magic);
        }
    }

    public static class next_page implements ActionListener {
        private boolean select;

        public next_page (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int lineCount = textArea.getLineCount ();
            int firstLine = textArea.getFirstLine ();
            int visibleLines = textArea.getVisibleLines ();
            int line = textArea.getCaretLine ();

            firstLine += visibleLines;

            if (firstLine + visibleLines >= lineCount - 1)
                firstLine = lineCount - visibleLines;

            textArea.setFirstLine (firstLine);

            int caret = textArea.getLineStartOffset (
                    Math.min (textArea.getLineCount () - 1,
                              line + visibleLines));
            if (select)
                textArea.select (textArea.getMarkPosition (), caret);
            else
                textArea.setCaretPosition (caret);
        }
    }

    public static class next_word implements ActionListener {
        private boolean select;

        public next_word (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int caret = textArea.getCaretPosition ();
            int line = textArea.getCaretLine ();
            int lineStart = textArea.getLineStartOffset (line);
            caret -= lineStart;

            String lineText = textArea.getLineText (textArea
                    .getCaretLine ());

            if (caret == lineText.length ()) {
                if (lineStart + caret == textArea.getDocumentLength ()) {
                    textArea.getToolkit ().beep ();
                    return;
                }
                caret++;
            }
            else {
                String noWordSep = (String) textArea.getDocument ().getProperty ("noWordSep");
                caret = TextUtilities.findWordEnd (lineText, caret, noWordSep);
            }

            if (select)
                textArea.select (textArea.getMarkPosition (),
                                 lineStart + caret);
            else
                textArea.setCaretPosition (lineStart + caret);
        }
    }

    public static class overwrite implements ActionListener {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            textArea.setOverwriteEnabled (
                    !textArea.isOverwriteEnabled ());
        }
    }

    public static class prev_char implements ActionListener {
        private boolean select;

        public prev_char (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int caret = textArea.getCaretPosition ();
            if (caret == 0) {
                textArea.getToolkit ().beep ();
                return;
            }

            if (select)
                textArea.select (textArea.getMarkPosition (),
                                 caret - 1);
            else
                textArea.setCaretPosition (caret - 1);
        }
    }

    public static class prev_line implements ActionListener {
        private boolean select;

        public prev_line (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int caret = textArea.getCaretPosition ();
            int line = textArea.getCaretLine ();

            if (line == 0) {
                textArea.getToolkit ().beep ();
                return;
            }

            int magic = textArea.getMagicCaretPosition ();
            if (magic == -1) {
                magic = textArea.offsetToX (line,
                                            caret - textArea.getLineStartOffset (line));
            }

            caret = textArea.getLineStartOffset (line - 1)
                    + textArea.xToOffset (line - 1, magic);
            if (select)
                textArea.select (textArea.getMarkPosition (), caret);
            else
                textArea.setCaretPosition (caret);
            textArea.setMagicCaretPosition (magic);
        }
    }

    public static class prev_page implements ActionListener {
        private boolean select;

        public prev_page (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int firstLine = textArea.getFirstLine ();
            int visibleLines = textArea.getVisibleLines ();
            int line = textArea.getCaretLine ();

            if (firstLine < visibleLines)
                firstLine = visibleLines;

            textArea.setFirstLine (firstLine - visibleLines);

            int caret = textArea.getLineStartOffset (
                    Math.max (0, line - visibleLines));
            if (select)
                textArea.select (textArea.getMarkPosition (), caret);
            else
                textArea.setCaretPosition (caret);
        }
    }

    public static class prev_word implements ActionListener {
        private boolean select;

        public prev_word (boolean select) {
            this.select = select;
        }

        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            int caret = textArea.getCaretPosition ();
            int line = textArea.getCaretLine ();
            int lineStart = textArea.getLineStartOffset (line);
            caret -= lineStart;

            String lineText = textArea.getLineText (textArea
                    .getCaretLine ());

            if (caret == 0) {
                if (lineStart == 0) {
                    textArea.getToolkit ().beep ();
                    return;
                }
                caret--;
            }
            else {
                String noWordSep = (String) textArea.getDocument ().getProperty ("noWordSep");
                caret = TextUtilities.findWordStart (lineText, caret, noWordSep);
            }

            if (select)
                textArea.select (textArea.getMarkPosition (),
                                 lineStart + caret);
            else
                textArea.setCaretPosition (lineStart + caret);
        }
    }

    public static class repeat implements ActionListener,
                                          InputHandler.NonRecordable {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            textArea.getInputHandler ().setRepeatEnabled (true);
            String actionCommand = evt.getActionCommand ();
            if (actionCommand != null) {
                textArea.getInputHandler ().setRepeatCount (
                        Integer.parseInt (actionCommand));
            }
        }
    }

    public static class toggle_rect implements ActionListener {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            textArea.setSelectionRectangular (
                    !textArea.isSelectionRectangular ());
        }
    }

    public static class insert_char implements ActionListener,
                                               InputHandler.NonRepeatable {
        public void actionPerformed (ActionEvent evt) {
            JEditTextArea textArea = getTextArea (evt);
            String str = evt.getActionCommand ();
            int repeatCount = textArea.getInputHandler ().getRepeatCount ();

            if (textArea.isEditable ()) {
                StringBuffer buf = new StringBuffer ();
                for (int i = 0; i < repeatCount; i++)
                    buf.append (str);
                textArea.overwriteSetSelectedText (buf.toString ());
            }
            else {
                textArea.getToolkit ().beep ();
            }
        }
    }
}
